/**
 * 
 */
package com.tiduyun.data.exchange.util.http;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import com.tiduyun.data.exchange.util.HttpUrlUtils;
import com.tiduyun.data.exchange.util.JSONUtils;
import com.tiduyun.data.exchange.util.http.config.CmpHttpClientBuilderAutoConfiguration;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * @author wuzheng@tiduyun.com
 *
 */
@Slf4j
public class BaseHttpApiClient {

    @Getter
    @Setter
    @EqualsAndHashCode
    public static class HttpApiRequest {
        private URI uri;
        private String method;
        private List<Header> headers;
        private String mimeType;
        private Charset charset;
        private List<NameValuePair> urlParams;
        private List<NameValuePair> formParams;
        private String body;
        private HttpClientContext httpClientContext;

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("HttpApiRequest [uri=");
            builder.append(uri);
            builder.append(", method=");
            builder.append(method);
            builder.append(", headers=");
            builder.append(headers);
            builder.append(", mimeType=");
            builder.append(mimeType);
            builder.append(", charset=");
            builder.append(charset);
            builder.append(", urlParams=");
            builder.append(urlParams);
            builder.append(", formParams=");
            builder.append(formParams);
            builder.append(", body=");
            builder.append(body);
            builder.append(", httpClientContext=");
            if (httpClientContext == null) {
                builder.append(httpClientContext);
            } else {
                try {
                    builder.append(JSONUtils.toJSONString(httpClientContext));
                } catch (Exception e) {
                    builder.append(httpClientContext);
                }
            }
            builder.append("]");
            return builder.toString();
        }

    }

    public static final String API_METHOD_GET = "GET";

    public static final String API_METHOD_POST = "POST";

    public static final String API_METHOD_PUT = "PUT";

    public static final String API_METHOD_DELETE = "DELETE";

    public static final String DEFAULT_MIME_TYPE = ContentType.APPLICATION_JSON.getMimeType();

    public static final Charset DEFAULT_CHARSET = Consts.UTF_8;

    private CloseableHttpClient httpClient;

    private HttpClientContext httpClientContext;

    @Autowired(required = false)
    @Qualifier(CmpHttpClientBuilderAutoConfiguration.BEAN_HTTP_CLIENT_BUILDER)
    protected HttpClientBuilder httpClientBuilder;

    @PostConstruct
    public void initClient() {
        if (httpClient == null && httpClientBuilder != null) {
            httpClient = httpClientBuilder.build();
        }
    }

    public String execute(HttpApiRequest apiRequest) throws Exception, UnexpectedStatusCodeException {
        preparedRequest(apiRequest);

        log.debug("execute request: request={}", apiRequest);

        HttpRequestBase httpRequest = createHttpRequest(apiRequest);

        try (CloseableHttpResponse response = executeHttpRequest(apiRequest, httpRequest)) {
            return parseHttpResponse(apiRequest, response);
        } catch (Exception e) {
            log.error("parse http response failed", e);
            throw e;
        }
    }

    protected void preparedRequest(HttpApiRequest apiRequest) {
        if (apiRequest.getCharset() == null) {
            apiRequest.setCharset(DEFAULT_CHARSET);
        }
        if (apiRequest.getMimeType() == null) {
            apiRequest.setMimeType(DEFAULT_MIME_TYPE);
        }
    }

    protected HttpRequestBase createHttpRequest(HttpApiRequest apiRequest) throws URISyntaxException {
        URI uri = createHttpUri(apiRequest);

        HttpRequestBase req = null;
        switch (apiRequest.getMethod()) {
            case API_METHOD_GET:
                req = new HttpGet(uri);
                break;
            case API_METHOD_POST:
                req = new HttpPost(uri);
                break;
            case API_METHOD_PUT:
                req = new HttpPut(uri);
                break;
            case API_METHOD_DELETE:
                req = new HttpDelete(uri);
                break;
            default:
                throw new IllegalArgumentException("Unsupport http method: " + apiRequest.getMethod());
        }

        List<Header> headers = apiRequest.getHeaders();
        if (CollectionUtils.isNotEmpty(headers)) {
            req.setHeaders(headers.toArray(new Header[headers.size()]));
        }
        if (req instanceof HttpEntityEnclosingRequestBase) {
            HttpEntityEnclosingRequestBase entityReq = (HttpEntityEnclosingRequestBase) req;
            entityReq.setEntity(createHttpEntity(apiRequest));
        }

        return req;
    }

    protected URI createHttpUri(HttpApiRequest apiRequest) throws URISyntaxException {
        return HttpUrlUtils.buildUri(apiRequest.getUri(), apiRequest.getUrlParams());
    }

    protected HttpEntity createHttpEntity(HttpApiRequest apiRequest) {
        List<NameValuePair> formParams = apiRequest.getFormParams();
        String body = apiRequest.getBody();
        Charset charset = apiRequest.getCharset();
        String mimeType = apiRequest.getMimeType();

        if (CollectionUtils.isNotEmpty(formParams)) {
            return new UrlEncodedFormEntity(formParams, charset);
        } else if (StringUtils.isNotEmpty(body)) {
            return new StringEntity(body, ContentType.create(mimeType, charset));
        }
        return null;
    }

    protected CloseableHttpResponse executeHttpRequest(HttpApiRequest apiRequest, HttpRequestBase httpRequest)
            throws ClientProtocolException, IOException {
        if (httpClientContext != null) {
            return httpClient.execute(httpRequest, httpClientContext);
        } else if (apiRequest.getHttpClientContext() != null) {
            return httpClient.execute(httpRequest, apiRequest.getHttpClientContext());
        } else {
            return httpClient.execute(httpRequest);
        }
    }

    protected <T extends NameValuePair> List<T> filterParams(List<T> params) {
        List<T> filteredParams = new ArrayList<>();
        for (T p : params) {
            if (StringUtils.isNotEmpty(p.getValue())) {
                filteredParams.add(p);
            }
        }
        return filteredParams;
    }

    protected String parseHttpResponse(HttpApiRequest apiRequest, CloseableHttpResponse response)
            throws UnsupportedOperationException, IOException, UnexpectedStatusCodeException {
        int statusCode = response.getStatusLine().getStatusCode();
        HttpEntity entity = response.getEntity();
        Charset charset = getHttpResponseCharset(apiRequest, response);
        String responseText = IOUtils.toString(entity.getContent(), charset);
        if (statusCode < 200 || statusCode > 299) {
            log.error("Request http api failed: statusCode={}, responseText={}, apiRequest={}", statusCode, responseText,
                    apiRequest);
            throw new UnexpectedStatusCodeException(statusCode);
        }
        return responseText;
    }

    protected Charset getHttpResponseCharset(HttpApiRequest apiRequest, CloseableHttpResponse response) {
        Charset charset = apiRequest.getCharset();
        try {
            Header contentTypeHeader = response.getFirstHeader("Content-Type");
            if (contentTypeHeader != null) {
                ContentType contentType = ContentType.parse(contentTypeHeader.getValue());
                if (contentType != null && contentType.getCharset() != null) {
                    charset = contentType.getCharset();
                }
            }
        } catch (Exception e) {
            // ignore errors
        }
        return charset;
    }

    public CloseableHttpClient getHttpClient() {
        return httpClient;
    }

    public void setHttpClient(CloseableHttpClient httpClient) {
        this.httpClient = httpClient;
    }

    public HttpClientContext getHttpClientContext() {
        return httpClientContext;
    }

    public void setHttpClientContext(HttpClientContext httpClientContext) {
        this.httpClientContext = httpClientContext;
    }

}
